package metrics_cli

import (
	"context"
	"errors"
	"fmt"
	"gluenet/internal/glue-srvp/adapter/metrics-cli/etcd"
	"gluenet/internal/glue-srvp/adapter/metrics-cli/prom"
	"gluenet/internal/glue-srvp/domain/dependency"
	"gluenet/internal/glue-srvp/domain/entity"
	"gluenet/internal/glue-srvp/domain/po"
	"gluenet/internal/pkg/infra"
	"gluenet/internal/pkg/infra/prepare"
	"path/filepath"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

func init() {
	prepare.RegisterRepo(func() {
		cli := NewMetricsCLI()
		prepare.InjectDependency(func() dependency.MetricCLI {
			return cli
		})
	})
}

func NewMetricsCLI() *Metrics {
	r := &Metrics{}
	return r
}

type Metrics struct {
}

func (m Metrics) MetricsNow(query string) ([]infra.MetricsData, error) {
	data, err := prom.CLI.Query(query, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return nil, err
	}
	if len(data.Result) == 0 {
		return nil, infra.DataNotFind
	}

	return data.Result, nil
}

func (m Metrics) GetInstanceState(GUID string) error {
	panic("implement me")
}

func (m Metrics) GetInstanceMetric(obj *entity.Instance, metric string) error {
	//data,err := prom.CLI.GetMetrics(map[string]string{
	//	"__name__":"info",
	//	"application_guid":obj.GUID,
	//	"platform":obj.Platform,
	//	"object":"application",
	//},fmt.Sprintf("%v",time.Now().Unix()))
	//if err !=nil {
	//	return err
	//}
	//containers := make(map[string]map[string]interface{},len(data.Result))
	//for _,v := range data.Result {
	//	contain := v.Metric["container"]
	//	nodeName := v.Metric["host_name"]
	//	containers[contain]= map[string]interface{}{
	//		"node_name": nodeName,
	//	}
	//}
	//obj.RunTimeRepoValue = obj
	return nil
}

func (m Metrics) UpdateInstanceContainerMetric(obj *entity.Instance) error {
	nodems, err := prom.CLI.GetMetrics(map[string]string{
		"__name__":         "info",
		"application_guid": obj.GUID,
		"platform":         obj.Platform,
		"object":           "application",
		"label":            "kube_pod_info",
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return err
	}
	podms, err := prom.CLI.GetMetrics(map[string]string{
		"__name__":         "info",
		"application_guid": obj.GUID,
		"platform":         obj.Platform,
		"object":           "application",
		"label":            "kube_pod_container_info",
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return err
	}
	status, err := prom.CLI.GetMetrics(map[string]string{
		"__name__":         "status",
		"application_guid": obj.GUID,
		"platform":         obj.Platform,
		"object":           "application",
		"label":            "container_status_information",
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return err
	}

	pods := make(map[string]string, len(nodems.Result))
	podsStatus := make(map[string]int)
	for _, v := range nodems.Result {
		pod := v.Metric["pod"]
		node := v.Metric["node"]
		//ic := po.InstanceContainer{NodeName: nodeName}
		pods[pod] = node
		podsStatus[pod] = infra.NotReady
	}

	containers := make(map[string]string, len(podms.Result))
	for _, v := range podms.Result {
		contain := v.Metric["container"]
		pod := v.Metric["pod"]
		//ic := po.InstanceContainer{NodeName: nodeName}
		containers[contain] = pod
	}

	var readyCount = 0
	for _, v := range status.Result {
		if v.Value[1].(string) == "1" {
			pod := v.Metric["pod"]
			podsStatus[pod] = infra.Ready
			readyCount++
		}
	}

	obj.RunTimeRepoValue.ContainerInPods = containers
	obj.RunTimeRepoValue.ContainerCount = int64(len(containers))
	obj.RunTimeRepoValue.PodsOnNodes = pods
	obj.RunTimeRepoValue.PodsCount = int64(len(pods))
	obj.RunTimeRepoValue.PodsState = podsStatus
	if readyCount < len(pods) {
		obj.RunTimeRepoValue.AppState = infra.NotReady
	} else {
		obj.RunTimeRepoValue.AppState = infra.Ready
	}
	return err
}

func (m Metrics) UpdateAgentNodesMetric(obj *entity.Agent) error {
	nodemCpu, err := prom.CLI.GetMetrics(map[string]string{
		"__name__":   "metrics",
		"agent_guid": obj.GUID,
		"platform":   obj.Platform,
		"object":     "machine",
		"label":      "machine_cpu_cores",
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return err
	}

	nodemMem, err := prom.CLI.GetMetrics(map[string]string{
		"__name__":   "metrics",
		"agent_guid": obj.GUID,
		"platform":   obj.Platform,
		"object":     "machine",
		"label":      "machine_memory_bytes",
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return err
	}

	nodesMap := make(map[string]*po.AgentNodeInfo, len(nodemCpu.Result))
	for _, v := range nodemCpu.Result {
		nodeName := v.Metric["host_name"]
		node := po.AgentNodeInfo{}
		node.NodeName = nodeName
		node.CpuCores = fmt.Sprintf("%v", v.Value[1])
		nodesMap[nodeName] = &node
	}
	for _, v := range nodemMem.Result {
		nodeName := v.Metric["host_name"]
		if _, ok := nodesMap[nodeName]; !ok {
			return fmt.Errorf("metrics don't have cpu or mem together")
		}
		nodesMap[nodeName].MemSize = fmt.Sprintf("%v", v.Value[1])
	}

	var nodes, i = make([]po.AgentNodeInfo, len(nodesMap)), 0
	for _, v := range nodesMap {
		if v.CpuCores == "" || v.MemSize == "" {
			return fmt.Errorf("metrics don't have cpu or mem together")
		}
		nodes[i] = *v
		i++
	}

	obj.RunTimeRepoValue.ClusterNodes = nodes
	return nil
}

//func (m Metrics) GetInstanceGraphJaeger(obj *entity.Instance) (interface{}, error) {
//	if obj.GUID == "" {
//		return nil, fmt.Errorf("key is null")
//	}
//	var key = filepath.ToSlash(filepath.Join(infra.JaegerDagRepoKey, obj.GUID))
//
//	res, err := etcd.DB.Get(context.Background(), key)
//	if err != nil {
//		return nil, err
//	}
//	if len(res.Kvs) > 1 {
//		return nil, fmt.Errorf("much key in etcd")
//	}
//	if len(res.Kvs) == 0 {
//		return nil, infra.DataNotFind
//	}
//
//	//fmt.Printf("\n\n%s\n\n",res.Kvs[0].Value)
//
//	var value interface{}
//	if err = yaml.Unmarshal(res.Kvs[0].Value, &value); err != nil {
//		return nil, err
//	}
//	return value, nil
//}

func (m Metrics) GetInstanceGraphCilium(obj *entity.Instance) (interface{}, error) {
	var nodes = make(map[string]interface{})
	var links = make([]map[string]interface{}, 0)
	// 1.Query all qualified services
	// Query all nodes that are source
	sourceNode, err := prom.CLI.FlexibleQuery(map[string]string{
		"__name__": "cilium_http_response_latence_count",
		"source":   obj.GUID,
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return nil, errors.New("cilium_http_response_latence does not exist")
	}
	for _, v := range sourceNode.Result {
		nodeName := v.Metric["source"][strings.Index(v.Metric["source"], "/")+1:]
		nameSpace := v.Metric["source"][:strings.Index(v.Metric["source"], "/")]
		cpuUsage, err := prom.CLI.GetMetrics(map[string]string{
			"__name__": "metrics",
			"label":    "container_cpu_usage",
			"pod":      nodeName,
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("metrics does not exist")
		}
		memUsage, err := prom.CLI.GetMetrics(map[string]string{
			"__name__": "metrics",
			"label":    "container_memory_used",
			"pod":      nodeName,
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("metrics does not exist")
		}
		nodes[nodeName] = map[string]interface{}{
			"id":             v.Metric["source"],
			"node_name":      nodeName,
			"node_namespace": nameSpace,
			"labels":         nil,
			"cpu":            cpuUsage.Result[0].Value[1],
			"mem":            memUsage.Result[0].Value[1],
			"disk":           []float64{2728.41, 45.67},
			"network":        []float64{792.41, 704.31},
			"port":           v.Metric["port"],
			"protocol":       v.Metric["protocol"],
		}
	}
	// Query all nodes that are destination
	destinationNode, err := prom.CLI.FlexibleQuery(map[string]string{
		"__name__":    "cilium_http_response_latence_count",
		"destination": obj.GUID,
	}, fmt.Sprintf("%v", time.Now().Unix()))
	if err != nil {
		return nil, errors.New("cilium_http_response_latence does not exist")
	}
	for _, v := range destinationNode.Result {
		nodeName := v.Metric["destination"][strings.Index(v.Metric["destination"], "/")+1:]
		nameSpace := v.Metric["destination"][:strings.Index(v.Metric["destination"], "/")]
		cpuUsage, err := prom.CLI.GetMetrics(map[string]string{
			"__name__": "metrics",
			"label":    "container_cpu_usage",
			"pod":      nodeName,
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("metrics does not exist")
		}
		memUsage, err := prom.CLI.GetMetrics(map[string]string{
			"__name__": "metrics",
			"label":    "container_memory_used",
			"pod":      nodeName,
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("metrics does not exist")
		}
		nodes[nodeName] = map[string]interface{}{
			"id":             v.Metric["destination"],
			"node_name":      nodeName,
			"node_namespace": nameSpace,
			"labels":         nil,
			"cpu":            cpuUsage.Result[0].Value[1],
			"mem":            memUsage.Result[0].Value[1],
			"disk":           []float64{2728.41, 45.67},
			"network":        []float64{792.41, 704.31},
			"port":           v.Metric["port"],
			"protocol":       v.Metric["protocol"],
		}
	}
	// 3.Collect link information
	for _, v := range nodes {
		ciliumHttpResponseLatency, err := prom.CLI.GetMetrics(map[string]string{
			"__name__":    "cilium_http_response_latence",
			"destination": v.(map[string]interface{})["id"].(string),
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("cilium_http_response_latence does not exist")
		}
		ciliumHttpResponseLatenceCount, err := prom.CLI.GetMetrics(map[string]string{
			"__name__":    "cilium_http_response_latence",
			"destination": v.(map[string]interface{})["id"].(string),
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("cilium_http_response_latence_count does not exist")
		}
		ciliumHttpResponseLatenceErrCount, err := prom.CLI.GetMetrics(map[string]string{
			"__name__":    "cilium_http_response_latence",
			"destination": v.(map[string]interface{})["id"].(string),
			"verdict":     "DROPPED",
		}, fmt.Sprintf("%v", time.Now().Unix()))
		if err != nil {
			return nil, errors.New("cilium_http_response_latence_count does not exist")
		}
		var requestErrCount float64
		var requestSuccessRate float64
		if len(ciliumHttpResponseLatenceErrCount.Result) == 0 {
			requestErrCount = 0
			requestSuccessRate = 1
		} else {
			requestErrCount = ciliumHttpResponseLatenceErrCount.Result[0].Value[0].(float64)
			requestSuccessRate = 1 - (ciliumHttpResponseLatenceErrCount.Result[0].Value[0].(float64) / ciliumHttpResponseLatenceCount.Result[0].Value[0].(float64))
		}
		for _, v := range ciliumHttpResponseLatency.Result {
			link := map[string]interface{}{
				"id":                   v.Metric["source"],
				"namespace":            v.Metric["namespace"],
				"source_pod":           v.Metric["source"],
				"source_ip":            v.Metric["sourceIp"],
				"destination_pod":      v.Metric["destination"],
				"destination_ip":       v.Metric["destinationIp"],
				"protocol":             v.Metric["protocol"],
				"url":                  v.Metric["url"],
				"port":                 v.Metric["port"],
				"method":               v.Metric["method"],
				"code":                 v.Metric["code"],
				"verdict":              v.Metric["verdict"],
				"transmission speed":   32.08,
				"request_count":        ciliumHttpResponseLatenceCount.Result[0].Value[0].(float64),
				"request_err_count":    requestErrCount,
				"request_success_rate": requestSuccessRate,
				"latency": map[string]interface{}{
					v.Metric["quantile"]: v.Value[0],
				},
				"service": v.Metric["service"],
			}
			links = append(links, link)
		}
	}
	dagResp := map[string]interface{}{
		"hubble_link":    links,
		"hubble_service": nodes,
	}
	return dagResp, nil
}

func (m Metrics) GetInstanceGraphLinkerd(obj *entity.Instance) (interface{}, error) {
	if obj.GUID == "" {
		return nil, fmt.Errorf("key is null")
	}
	var key = filepath.ToSlash(filepath.Join(infra.LinkerdDagRepoKey, obj.GUID))

	res, err := etcd.DB.Get(context.Background(), key)
	if err != nil {
		return nil, err
	}
	if len(res.Kvs) > 1 {
		return nil, fmt.Errorf("much key in etcd")
	}
	if len(res.Kvs) == 0 {
		return nil, infra.DataNotFind
	}

	//fmt.Printf("\n\n%s\n\n",res.Kvs[0].Value)

	var value interface{}
	if err = yaml.Unmarshal(res.Kvs[0].Value, &value); err != nil {
		return nil, err
	}
	return value, nil
}

//
//func (m Metrics) UpdateInstanceGraphLinkerd(obj *entity.Instance) error {
//	if obj.Platform == infra.PlatformGluenets {
//		return fmt.Errorf("gluenet not support linkerd")
//	}
//
//	var nodes = make(map[string]interface{})
//
//	if len(obj.RunTimeRepoValue.Agent) == 0 {
//		return fmt.Errorf("can't not find agent")
//	}
//	if len(obj.RunTimeRepoValue.Containers) == 0 {
//		return fmt.Errorf("runtime value is not contain containers")
//	} else {
//		for contain, info := range obj.RunTimeRepoValue.Containers {
//			nodes[contain] = map[string]interface{}{
//				"type":           "container",
//				"info":           info,
//				"node_guid":      obj.RunTimeRepoValue.Agent,
//				"node_type":      "machine",
//				"platfrom":       obj.Platform,
//				"container_name": contain,
//			}
//		}
//	}
//
//	tcpOpen, err := prom.CLI.GetMetrics(map[string]string{
//		"__name__":         "metrics",
//		"application_guid": obj.GUID,
//		"platform":         obj.Platform,
//		//"object":"application",
//		"label": "tcp_open_total",
//	}, fmt.Sprintf("%v", time.Now().Unix()))
//	if err != nil {
//		return err
//	}
//	var links = make([]interface{}, len(tcpOpen.Result))
//	for k, v := range tcpOpen.Result {
//		source := v.Metric["container"]
//		target := v.Metric["dst_container"]
//		value := v.Value[1]
//		link := map[string]interface{}{
//			"source": source,
//			"target": target,
//			"value":  value,
//		}
//		links[k] = link
//	}
//
//	dag := map[string]interface{}{
//		"dag_nodes": nodes,
//		"dag_links": links,
//	}
//
//	bts, err := yaml.Marshal(dag)
//	if err != nil {
//		return err
//	}
//	_, err = etcd.DB.Put(context.Background(), filepath.ToSlash(filepath.Join(infra.LinkerdDagRepoKey, obj.GUID)), string(bts))
//	return err
//}
//
//func (m Metrics) UpdateInstanceGraphJaeger(obj *entity.Instance) error {
//	switch obj.Platform {
//	case infra.PlatformKubernetes:
//		var nodes = make(map[string]interface{})
//
//		if len(obj.RunTimeRepoValue.Agent) == 0 {
//			return fmt.Errorf("can't not find agent")
//		}
//		if len(obj.RunTimeRepoValue.Containers) == 0 {
//			return fmt.Errorf("runtime value is not contain containers")
//		} else {
//			for contain, info := range obj.RunTimeRepoValue.Containers {
//				nodes[contain] = map[string]interface{}{
//					"type":           "container",
//					"info":           info,
//					"node_guid":      obj.RunTimeRepoValue.Agent,
//					"node_type":      "machine",
//					"platfrom":       obj.Platform,
//					"container_name": contain,
//				}
//			}
//		}
//
//		dagLink, err := jaeger.CLI.DrawK8sLink(obj.GUID)
//		//dagLink,err := jaeger.CLI.DrawK8sLink("f18af130113b0db7554d46d9a994b873")
//		if err != nil {
//			return err
//		}
//
//		dag := map[string]interface{}{
//			"dag_nodes": nodes,
//			"dag_links": dagLink,
//		}
//
//		bts, err := yaml.Marshal(dag)
//		if err != nil {
//			return err
//		}
//		_, err = etcd.DB.Put(context.Background(), filepath.ToSlash(filepath.Join(infra.JaegerDagRepoKey, obj.GUID)), string(bts))
//		return err
//		// case infra.PlatformGluenets:
//		// 	var containers []string
//		// 	var nodes = make(map[string]interface{})
//		// 	if runI, ok := obj.RunTimeRepoValue.(map[string]interface{}); !ok {
//		// 		return fmt.Errorf("runtime value is not map[string]interface{}")
//		// 	} else {
//		// 		agentsI, ok := runI["agents"]
//		// 		if !ok {
//		// 			return fmt.Errorf("can't not find agent")
//		// 		}
//		// 		agents, ok := agentsI.(map[string]interface{})
//		// 		if !ok {
//		// 			return fmt.Errorf("agents is not map")
//		// 		}
//
//		// 		if run, ok := runI["containers"]; !ok {
//		// 			return fmt.Errorf("runtime value is not contain containers")
//		// 		} else {
//		// 			if cts, ok := run.(map[string]interface{}); !ok {
//		// 				return fmt.Errorf("contianer is not map[string]interface{}")
//		// 			} else {
//		// 				for contain, info := range cts {
//		// 					containers = append(containers, contain)
//		// 					nodes[contain] = map[string]interface{}{
//		// 						"type":           "container",
//		// 						"info":           info,
//		// 						"node_guid":      agents[contain],
//		// 						"node_type":      "machine",
//		// 						"platfrom":       obj.Platform,
//		// 						"container_name": contain,
//		// 					}
//		// 				}
//		// 			}
//		// 		}
//		// 	}
//		// 	dagLink, err := jaeger.CLI.DrawGlueLink(obj.GUID, containers)
//		// 	if err != nil {
//		// 		return err
//		// 	}
//
//		// 	dag := map[string]interface{}{
//		// 		"dag_nodes": nodes,
//		// 		"dag_links": dagLink,
//		// 	}
//
//		// 	bts, err := yaml.Marshal(dag)
//		// 	if err != nil {
//		// 		return err
//		// 	}
//		// 	_, err = etcd.DB.Put(context.Background(), filepath.ToSlash(filepath.Join(infra.JaegerDagRepoKey, obj.GUID)), string(bts))
//		// 	return err
//	}
//	return fmt.Errorf("unknown platform")
//}

func (m Metrics) GetInstanceContainerState(GUID, container string) error {
	panic("implement me")
}

func (m Metrics) GetInstanceContainerMetric(GUID, container, metric string) error {
	panic("implement me")
}

func (m Metrics) GetInstanceContainerLog(GUID, container, metric string) error {
	panic("implement me")
}

func (m Metrics) GetAgentState(GUID string) error {
	panic("implement me")
}

func (m Metrics) GetAgentMetric(GUID, metric string) error {
	panic("implement me")
}

func (m Metrics) GetAgentHostState(GUID, hostname string) error {
	panic("implement me")
}

func (m Metrics) GetAgentHostMetric(GUID, hostname, metric string) error {
	panic("implement me")
}
